package xin.marcher.module.message.manager.common;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import xin.marcher.module.common.redis.RedisCache;
import xin.marcher.module.common.redis.RedisLock;
import xin.marcher.module.common.utils.diplomacy.RedisKeyUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static xin.marcher.module.common.constants.diplomacy.BaseConstants.*;

/**
 * 缓存读写管理
 */
@Slf4j
@Service
public class RedisReadWriteManager {

    @Resource
    private RedisCache redisCache;

    @Resource
    private RedisLock redisLock;

    /**
     * 批量获取缓存数据
     *
     * @param key                 关键字列表
     * @param clazz               需要将缓存JSON转换的对象
     * @param getRedisKeyFunction 获取redis key的方法
     * @param getDbFunction       获取数据源对象的方法
     * @return java.util.Optional<java.util.List < T>>
     */
    public <T> Optional<List<T>> listRedisStringDataByCache(Long key, Class<T> clazz,
                                                            Function<Long, String> getRedisKeyFunction,
                                                            Function<Long, Optional<List<T>>> getDbFunction) {
        try {
            String redisKey = getRedisKeyFunction.apply(key);
            // 过滤无效缓存
            String cache = redisCache.get(redisKey);
            if (EMPTY_OBJECT_STRING.equals(cache)) {
                return Optional.empty();
            }

            if (StringUtils.isNotBlank(cache)) {
                List<T> list = JSON.parseArray(cache, clazz);
                return Optional.of(list);
            }

            // 缓存没有则读库
            return listRedisStringDataByDb(key, getRedisKeyFunction, getDbFunction);
        } catch (Exception e) {
            log.error("获取缓存数据异常 key={},clazz={}", key, clazz, e);
            throw e;
        }
    }


    /**
     * 读取数据库表数据赋值到redis
     *
     * @param key
     * @param getRedisKeyFunction
     * @param getDbFunction
     * @return java.util.Optional<T>
     */
    public <T> Optional<List<T>> listRedisStringDataByDb(Long key, Function<Long, String> getRedisKeyFunction,
                                                         Function<Long, Optional<List<T>>> getDbFunction) {
        if (Objects.isNull(key) || Objects.isNull(getDbFunction)) {
            return Optional.empty();
        }

        try {
            if (!redisLock.lock(String.valueOf(key))) {
                return Optional.empty();
            }
            String redisKey = getRedisKeyFunction.apply(key);
            Optional<List<T>> optional = getDbFunction.apply(key);

            putCacheString(redisKey, optional);
            return optional;
        } finally {
            redisLock.unlock(String.valueOf(key));
        }
    }

    private void putCacheString(String redisKey, Optional optional) {
        if (!optional.isPresent()) {
            // 把空对象暂存到redis
            redisCache.setex(redisKey, EMPTY_OBJECT_STRING,
                    RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_ONE_DAY, TimeUnit.HOURS, NUMBER_24));
            log.warn("发生缓存穿透 redisKey={}", redisKey);
            return;
        }
        // 把表数据对象存到redis
        redisCache.setex(redisKey, JSON.toJSONString(optional.get()),
                RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_SEVEN_DAYS));
        log.info("表数据对象存到redis redisKey={}, data={}", redisKey, JSON.toJSONString(optional.get()));
    }


    /**
     * 批量获取缓存数据
     *
     * @param key                 关键字列表
     * @param clazz               需要将缓存JSON转换的对象
     * @param getRedisKeyFunction 获取redis key的方法
     * @param getDbFunction       获取数据源对象的方法
     * @return java.util.Optional<java.util.List < T>>
     */
    public <T> Optional<T> getRedisStringDataByCache(String key, Class<T> clazz,
                                                     Function<String, String> getRedisKeyFunction,
                                                     Function<String, Optional<T>> getDbFunction) {
        try {
            String redisKey = getRedisKeyFunction.apply(key);
            String cache = redisCache.get(redisKey);
            // 过滤无效缓存
            if (EMPTY_OBJECT_STRING.equals(cache)) {
                return Optional.empty();
            }
            if (StringUtils.isNotBlank(cache)) {
                T t = JSON.parseObject(cache, clazz);
                return Optional.of(t);
            }
            // 缓存没有则读库
            return getRedisStringDataByDb(key, getRedisKeyFunction, getDbFunction);
        } catch (Exception e) {
            log.error("获取缓存数据异常 key={},clazz={}", key, clazz, e);
            throw e;
        }
    }

    /**
     * 读取数据库表数据赋值到redis
     *
     * @param key
     * @param getRedisKeyFunction
     * @param getDbFunction
     * @return java.util.Optional<T>
     */
    public <T> Optional<T> getRedisStringDataByDb(String key, Function<String, String> getRedisKeyFunction,
                                                  Function<String, Optional<T>> getDbFunction) {
        if (StringUtils.isBlank(key) || Objects.isNull(getDbFunction)) {
            return Optional.empty();
        }

        try {
            if (!redisLock.lock(key)) {
                return Optional.empty();
            }
            String redisKey = getRedisKeyFunction.apply(key);
            Optional<T> optional = getDbFunction.apply(key);

            putCacheString(redisKey, optional);
            return optional;
        } finally {
            redisLock.unlock(key);
        }
    }

}